#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <cstring>
#include <cassert>
#include <cstdlib>
#include <functional>
#include <wait.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
// 包含unix头文件 fork

#include "Protocol.hpp"

namespace server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };

    static const uint16_t gport = 8080;
    static const int gbacklog = 5;

    using func_t = function<bool (const HttpRequest &, HttpResponse &)>;

    class HttpServer
    {
    public:
        HttpServer(func_t func, const uint16_t &port = gport)
            : _func(func), _listenSockfd(-1), _port(port)
        {
        }
       
        void initServer() //初始化服务器
        {
            // 1.创建TCP套接字
            _listenSockfd = socket(AF_INET, SOCK_STREAM, 0);
            if (_listenSockfd == -1)
            {
                exit(SOCKET_ERR);
            }

            // 2.bind绑定自己的网络信息, ip+port
            struct sockaddr_in local; // 服务器本地的网络信息
            memset(&local, 0, sizeof(local));
            local.sin_family = PF_INET; // IP protocol family
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = INADDR_ANY; // 任意ip
            // 为什么recvfrom sendto数据时, 没有hton ntoh呢？因为系统调用已经隐式的做了网络字节序和主机的转换工作.而我们自己写的结构体必须得转换之后才能bind

            if (bind(_listenSockfd, (struct sockaddr *)&local, sizeof(local)) == -1)
            {
                exit(BIND_ERR);
            }

            // 3.设置服务器的socket为监听状态 --> 监听什么时候客户端发来连接请求
            if (listen(_listenSockfd, gbacklog) == -1) // 第二个参数gbacklog后面再讲
            {
                exit(LISTEN_ERR);
            }
        }
        void HandlerHttp(int sockfd)
        {   
            // 1.读到完整的http请求
            // 2.反序列化
            // 3.得到一个HttpRequest，调用_func(HttpRequest, HttpResponse)
            // 4.对resp进行序列化
            // 5.将序列化后的resp，send回客户端即可

            char buffer[4096];//把定义出来的变量都放到一开始，在多线程里面是一个很好的习惯！！！
            HttpRequest req;
            HttpResponse resp;
            size_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);//大概率我们直接能读取到完整的http请求
            if(n > 0)
            {
                buffer[n] = 0;
                req._inbuffer = buffer;
                req.parse();
                _func(req, resp);
                send(sockfd, resp._outbuffer.c_str(), resp._outbuffer.size(), 0);
            }
        }
        void start() // 
        {
            for (;;)
            {
                // 4.server和client要建立连接, server获取来自client的连接请求
                struct sockaddr_in peer; // 远端的客户端信息
                socklen_t len = sizeof(peer);
                int sockfd = accept(_listenSockfd, (struct sockaddr *)&peer, &len); // sockfd是未来和client进行通信的套接字
                if (sockfd == -1)
                {
                    continue;
                }

                //---多进程方案: 孙子进程执行读取消息和消息的业务逻辑处理
                pid_t id = fork();
                assert(id >= 0); // 不允许创建进程出错
                if (id == 0)     // child process
                {
                    close(_listenSockfd);
                    if (fork() > 0)
                        exit(0); // 当前子进程退出, 创建出孙子进程, 让孙子进程提供服务, 孙子进程是孤儿进程, 会被操作系统领养回收

                    //接下来是孙子进程的代码 TODO
                    HandlerHttp(sockfd);
                    close(sockfd);
                    exit(0);
                }
                // father
                close(sockfd);
                waitpid(id, nullptr, 0); // 阻塞式等待
            }
        }
        ~HttpServer() {}

    private:
        int _listenSockfd; // 核心作用不是用来通信的, 而是用来监听客户端是否有连接请求的
        uint16_t _port;
        func_t _func;// http服务器对请求进行处理的函数
    };

} // namespace server

// while :; do ps -aL | head -1 && ps -aL | grep tcpServer; sleep 1; echo "#######################"; done ---查看线程的监控脚本
